package city.spring.modules.system.service.impl;

import city.spring.modules.ext.EntityExtUtils;
import city.spring.modules.ext.entity.MenuPermissionExt;
import city.spring.modules.ext.entity.MenuRoleExt;
import city.spring.modules.ext.service.MenuPermissionExtService;
import city.spring.modules.ext.service.MenuRoleExtService;
import city.spring.modules.system.dto.MenuViewDTO;
import city.spring.modules.system.entity.MenuEntity;
import city.spring.modules.system.entity.PermissionEntity;
import city.spring.modules.system.entity.RoleEntity;
import city.spring.modules.system.entity.UserEntity;
import city.spring.modules.system.repository.MenuRepository;
import city.spring.modules.system.service.MenuService;
import city.spring.modules.system.service.PermissionService;
import city.spring.modules.system.service.RoleService;
import city.spring.utils.RoleUtils;
import city.spring.utils.TreeDataUtils;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ListMultimap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.lang.NonNull;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;
import java.util.stream.Collectors;

/**
 * Service：菜单信息
 *
 * @author 侯坤林
 * @date 2020-04-12 15:33:27
 */
@CacheConfig(cacheNames = {MenuServiceImpl.CACHE_NAME})
@Transactional(rollbackFor = Throwable.class)
@Service
public class MenuServiceImpl extends ServiceImpl<MenuRepository, MenuEntity> implements MenuService {
    private static final Logger logger = LoggerFactory.getLogger(MenuServiceImpl.class);
    private final RoleService roleService;
    private final PermissionService permissionService;
    private final MenuRoleExtService menuRoleExtService;
    private final MenuPermissionExtService menuPermissionExtService;

    public MenuServiceImpl(RoleService roleService, PermissionService permissionService, MenuRoleExtService menuRoleExtService, MenuPermissionExtService menuPermissionExtService) {
        this.roleService = roleService;
        this.permissionService = permissionService;
        this.menuRoleExtService = menuRoleExtService;
        this.menuPermissionExtService = menuPermissionExtService;
    }

    @Override
    public MenuEntity getMenuInfo(String primaryKey, boolean loadPermissions, boolean loadRoles) {
        MenuEntity entity = getById(primaryKey);
        if (loadPermissions) {
            loadPermissions(entity);
        }
        if (loadRoles) {
            loadRoles(entity);
        }
        return entity;
    }

    @Override
    public void loadPermissions(MenuEntity entity) {
        entity.setPermissions(permissionService.getMenuPermissions(entity.getId()));
    }

    @Override
    public void loadRoles(MenuEntity entity) {
        entity.setRoles(roleService.getMenuRolesWithAuthority(entity.getId()));
    }

    @Override
    public void saveMenu(MenuEntity entity) {
        if (!save(entity)) {
            throw new RuntimeException("保存信息失败");
        }
        // repairRelation(entity);
    }

    @Override
    public void updateMenu(MenuEntity entity) {
        // 使用 updateById(entity); 修改数据时，将会修改 entity 对象中所有非null数据，如果某个字段为null，将会忽略该字段的修改
        if (entity.getId().equals(entity.getPid())) {
            throw new RuntimeException("当前对象的父级信息错误，不能与当前对象相同");
        }
        boolean update = updateById(entity);
        if (!update) {
            throw new RuntimeException("修改信息失败");
        }
        // repairRelation(entity);
    }

    @Override
    public void deleteMenu(String primaryKey) {
        removeById(primaryKey);
        menuRoleExtService.lambdaUpdate().eq(MenuRoleExt::getMenuId, primaryKey).remove();
        menuPermissionExtService.lambdaUpdate().eq(MenuPermissionExt::getMenuId, primaryKey).remove();
    }

    @Override
    public void deleteMenu(List<String> primaryKeys) {
        removeByIds(primaryKeys);
        menuRoleExtService.lambdaUpdate().in(MenuRoleExt::getMenuId, primaryKeys).remove();
        menuPermissionExtService.lambdaUpdate().in(MenuPermissionExt::getMenuId, primaryKeys).remove();
    }

    @Override
    public void maintainParentAndSorted(List<MenuEntity> entities) {
        List<MenuEntity> list = new ArrayList<>();
        for (MenuEntity entity : entities) {
            MenuEntity menuEntity = new MenuEntity();
            menuEntity.setId(entity.getId());
            menuEntity.setPid(entity.getPid());
            menuEntity.setSorted(entity.getSorted());
            list.add(menuEntity);
        }
        updateBatchById(list);
    }

    @Override
    public List<MenuEntity> converterToTreeDatasource(@NonNull List<? extends MenuEntity> rawDatasource, boolean loadParentEntity) {
        ListMultimap<String, MenuEntity> multimap;
        multimap = TreeDataUtils.handlerTreeDatasource(rawDatasource, "",
                MenuEntity::getId, MenuEntity::getPid,
                MenuEntity::getChildren, MenuEntity::setChildren);
        if (loadParentEntity) {
            return TreeDataUtils.loadTreeParents(multimap, "", lambdaQuery(),
                    MenuEntity::getId, MenuEntity::setChildren);
        }
        return new ArrayList<>(multimap.values());
    }

    /**
     * 维护 <strong>菜单信息</strong> 的 角色列表、权限列表
     *
     * @param entity <strong>菜单信息</strong>
     */
    private void repairRelation(MenuEntity entity) {
        EntityExtUtils.repairRelation(menuRoleExtService, entity,
                MenuEntity::getId, MenuEntity::getRoles, RoleEntity::getId,
                MenuRoleExt::new, MenuRoleExt::getMenuId);

        EntityExtUtils.repairRelation(menuPermissionExtService, entity,
                MenuEntity::getId, MenuEntity::getPermissions, PermissionEntity::getId,
                MenuPermissionExt::new, MenuPermissionExt::getMenuId);
    }

    @Cacheable(key = "targetClass.name + '.' + methodName")
    @Override
    public List<MenuViewDTO> getAllMenuView() {
        return baseMapper.getAllMenuView();
    }

    @Override
    public List<MenuViewDTO> filterUserMenuList(UserEntity user, List<MenuViewDTO> menus) {
        List<GrantedAuthority> authorities = user.getAuthorities();
        // 获取当前用户所有的权限名称
        Set<String> permissions = authorities.stream()
                .map(GrantedAuthority::getAuthority)
                .filter(item -> !item.startsWith(RoleUtils.getDefaultRolePrefix()))
                .collect(Collectors.toSet());
        // 获取当前用户所有的角色名称
        Set<String> roles = authorities.stream()
                .map(GrantedAuthority::getAuthority)
                .filter(item -> item.startsWith(RoleUtils.getDefaultRolePrefix()))
                .map(RoleUtils::getAuthorityRemoveDefaultPrefix)
                .collect(Collectors.toSet());
        // 用户所拥有的菜单列表信息
        List<MenuViewDTO> userMenus = filterAuthorityMenuViews(menus, permissions, roles);

        // 存有菜单角色信息的对象
        ArrayListMultimap<String, RoleEntity> roleMultimap = ArrayListMultimap.create();
        // 存有菜单权限信息的对象
        ArrayListMultimap<String, PermissionEntity> permissionMultimap = ArrayListMultimap.create();
        // 从菜单列表中取出角色信息和权限信息
        collectAuthorityList(userMenus, roleMultimap, permissionMultimap);

        // 根据菜单ID去重
        List<MenuViewDTO> singleMenus = menus.stream().collect(
                Collectors.collectingAndThen(
                        Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(MenuViewDTO::getId))), ArrayList::new)
        );
        // 把权限、角色信息重新放到去重后的菜单里面
        singleMenus.forEach(menu -> {
            menu.setRoles(roleMultimap.get(menu.getId()));
            menu.setPermissions(permissionMultimap.get(menu.getId()));
            // 清空不需要的信息
            menu.setAuthorityRoleId(null);
            menu.setAuthorityPermissionId(null);
            menu.setAuthorityCode(null);
            menu.setAuthorityTitle(null);
            menu.setAuthorityRemarks(null);
        });

        return singleMenus;
    }

    /**
     * 过滤拥有指定权限、角色的菜单列表信息
     *
     * @param menus          所有菜单视图列表
     * @param permissionKeys 权限key
     * @param roleKeys       角色key
     * @return 拥有这些权限的菜单列表信息
     */
    private List<MenuViewDTO> filterAuthorityMenuViews(List<MenuViewDTO> menus, Set<String> permissionKeys, Set<String> roleKeys) {
        // 过滤拥有指定权限、角色的菜单列表信息
        return menus.stream().filter(menu -> {
            String roleId = menu.getAuthorityRoleId();
            String permissionId = menu.getAuthorityPermissionId();
            String authorityCode = menu.getAuthorityCode();
            if (permissionId != null) {
                return permissionKeys.contains(authorityCode);
            } else if (roleId != null) {
                return roleKeys.contains(authorityCode);
            }
            // 不需要权限即可访问
            return true;
        }).collect(Collectors.toList());
    }

    /**
     * 从菜单列表中收集每个菜单的权限、角色列表信息
     *
     * @param menus              菜单列表
     * @param roleMultimap       存有角色信息的对象
     * @param permissionMultimap 存有权限信息的对象
     */
    private void collectAuthorityList(List<MenuViewDTO> menus,
                                      ArrayListMultimap<String, RoleEntity> roleMultimap,
                                      ArrayListMultimap<String, PermissionEntity> permissionMultimap) {
        // 单独取出所有的权限信息、角色信息
        menus.forEach(menu -> {
            // roleId 和 permissionId 其中一个必定为null（不可能同时有内容），如果两个都为null，则表示未分配权限，所有人可以方位
            String roleId = menu.getAuthorityRoleId();
            String permissionId = menu.getAuthorityPermissionId();
            String menuId = menu.getId();
            if (permissionId != null) {
                PermissionEntity permissionEntity = new PermissionEntity(menu.getAuthorityCode(), menu.getAuthorityTitle());
                permissionEntity.setId(permissionId);
                permissionMultimap.put(menuId, permissionEntity);
            } else if (roleId != null) {
                RoleEntity roleEntity = new RoleEntity(menu.getAuthorityCode(), menu.getAuthorityTitle());
                roleEntity.setId(roleId);
                roleMultimap.put(menuId, roleEntity);
            }
        });
    }

    @Override
    public void setMenuRoles(MenuEntity entity) {
        EntityExtUtils.repairRelation(menuRoleExtService, entity,
                MenuEntity::getId, MenuEntity::getRoles, RoleEntity::getId,
                MenuRoleExt::new, MenuRoleExt::getMenuId);
    }

    @Override
    public void setMenuPermissions(MenuEntity entity) {
        EntityExtUtils.repairRelation(menuPermissionExtService, entity,
                MenuEntity::getId, MenuEntity::getPermissions, PermissionEntity::getId,
                MenuPermissionExt::new, MenuPermissionExt::getMenuId);
    }
}